/**
 * ds18b20.c - DS18B20 功能封装
 * by Nixsawe
 * 2022-1-5 ~ 2022-1-9
 * 
 * 不支持寄生电源模式
 */

#include "ds18b20.h"

/*----------  基本方法  ----------*/

// 传入 0 时延时 12 周期
// 基准延时 (11 + 6 * n) 个周期
// 11.0592MHz 晶振下 9216 个周期相当于 10ms
void delay(unsigned char n)
{
	while(n --);
}

// 进行 CRC8 校验
// 返回计算得到的校验和，若最后一个字节的内容为校验和，则校验通过后本函数应返回 0
unsigned char crc8_check(unsigned char * bytes, unsigned char n)
{
	unsigned char i, j, crc = 0;
	for(i = 0; i < n; i ++)
	{
		crc ^= bytes[i];
		for(j = 0; j < 8; j ++)
		{
			if(crc & 1)
			{
				crc = (crc >> 1) ^ 0x8C;
			}
			else
			{
				crc >>= 1;
			}
		}
	}

	return crc;
}

// 等待操作完成
// 某些操作提供进度指示，可以用这样的方法进行等待
unsigned char ds18b20_wait_finish(int wait_ms)
{
	// 不断读 1 位数据，直到读到 1，或者超时返回
	// 每一个循环约 1ms
	do
	{
		// 延时约 936us
		delay(142);
		// 读操作大约 60us
		if(ds18b20_read_bit())
		{
			// 转换完毕
			return 1;
		}
	} while(-- wait_ms);

	return 0;
}

// 将 DS18B20 复位
// 执行每个新的操作前，都需要一步复位操作
// 如果线上有设备存在（初始化成功），则返回 1，否则返回 0
unsigned char ds18b20_reset()
{
	unsigned char ack;

	// 拉低电平
	DS18B20_DATA_IO = 0;

	// 持续至少 480us
	delay(72);

	// 释放引脚
	DS18B20_DATA_IO = 1;

	// 等待 15us~60us
	delay(5);	// 约 44.5us

	// 检测存在性
	// 如果设备响应，这条线将在该时间段被拉低
	ack = !DS18B20_DATA_IO;

	delay(67);

	return ack;
}

// 在总线上写 1 位数据
void ds18b20_write_bit(unsigned char dat)
{
	// 拉低电平
	DS18B20_DATA_IO = 0;
	// 等待约 1us
	_nop_();
	if(dat & 1)
	{
		// 拉高/释放电平
		DS18B20_DATA_IO	= 1;
	}
	// 等待约 60us
	delay(8);	// 约 64us
	// 拉高/释放电平
	DS18B20_DATA_IO	= 1;
}

// 在总线上读 1 位数据
unsigned char ds18b20_read_bit()
{
	unsigned char dat;
	// 拉低电平
	DS18B20_DATA_IO = 0;
	// 等待约 1us
	_nop_();
	// 释放电平
	DS18B20_DATA_IO = 1;
	// 读取电平
	dat = !!DS18B20_DATA_IO;
	// 等待约 60us
	delay(7);	// 约 57.5us
	return dat;
}

// 写字节，先传输低位
void ds18b20_write_byte(unsigned char dat)
{
	unsigned char i;
	for(i = 0; i < 8; i ++)
	{
		ds18b20_write_bit(dat & 1);
		dat >>= 1;
	}
}

// 读字节，先传输低位
unsigned char ds18b20_read_byte()
{
	unsigned char dat = 0, i;
	for(i = 0; i < 8; i ++)
	{
		dat >>= 1;
		if(ds18b20_read_bit())
			dat |= 0x80;
	}

	return dat;
}


/*----------  ROM 命令  ----------*/

// 通过二分法学习/搜索总线上的设备 ROM 列表 (TODO)
// unsigned char ds18b20_search_rom()
// {}

// 获取总线上下一个设备的 ROM 编码 (TODO)
// unsigned char ds18b20_search_next()
// {}

// 使用 ROM 命令读 64 位的 ROM 编码
// 当总线上仅有一个设备时，读取设备的 ROM 编码
// 有多个设备时会导致冲突
// 读取的数据校验成功时返回 1，否则返回 0
unsigned char ds18b20_read_rom(DS18B20_ROM_CODE * rom)
{
	unsigned char i;

	// 写 Read ROM 命令
	ds18b20_write_byte(DS18B20_CMD_READ_ROM);

	// 读 64 位（8 字节）的 ROM 编码
	for(i = 0; i < 8; i ++)
	{
		// 按字节读
		rom -> rom_code[i] = ds18b20_read_byte();
	}

	// 对数据进行 CRC 校验，并返回校验结果
	return !crc8_check(rom -> rom_code, 8);
}

// 根据 ROM 编码匹配设备
// 这个命令执行后，只有 ROM 编码完全匹配的设备会响应后续操作
void ds18b20_match_rom(DS18B20_ROM_CODE * rom)
{
	unsigned char i;

	// 写 Match ROM 命令
	ds18b20_write_byte(DS18B20_CMD_MATCH_ROM);

	// 写 64 位（8 字节）的 ROM 编码
	for(i = 0; i < 8; i ++)
	{
		// 按字节写
		ds18b20_write_byte(rom -> rom_code[i]);
	}
}

// 跳过 ROM 匹配
// 这个命令执行后，所有设备都会同步响应后续操作
// 有些命令是可以多设备同时进行的，比如可让总线上所有设备同时地执行温度转换命令
void ds18b20_skip_rom()
{
	// 写 Skip ROM 命令
	ds18b20_write_byte(DS18B20_CMD_SKIP_ROM);
}

// 报警 ROM 搜索
// Alarm Search
// TODO


/*----------  功能命令  ----------*/

// 进行温度转换
// 通知设备进行温度转换，设备将进行温度采样并结果存储在暂存器的温度寄存器中
// 温度转换的时间与采样精度（分辨率位数）有关
//    9-bit: <93.75ms
//   10-bit: <187.5ms
//   11-bit: <375ms
//   12-bit: <750ms
// 本方法在命令发出后立即返回，不等待命令执行完毕
void ds18b20_convert()
{
	// 写 Convert T 命令
	ds18b20_write_byte(DS18B20_CMD_CONVERT_T);
}

// 进行温度转换
// 同上，但本方法将等待温度转换操作结束后返回
// 当转换完成时，返回 1，转换超时则返回 0
unsigned char ds18b20_convert_sync()
{
	// 写 Convert T 命令
	ds18b20_write_byte(DS18B20_CMD_CONVERT_T);

	// 等待操作完成，超时时间根据最大转换时间估计
	return ds18b20_wait_finish(751);
}

// 写暂存器 (Scratchpad)
// 向设备的暂存器的 TH、TL 寄存器以及配置 (configuration) 寄存器写数据
void ds18b20_write_pad(DS18B20_TEMP_T th, DS18B20_TEMP_T tl, unsigned char cfg)
{
	// 写 Write Scratchpad 命令
	ds18b20_write_byte(DS18B20_CMD_WRITE_PAD);

	// 写 TH 字节
	ds18b20_write_byte(th);

	// 写 TL 字节
	ds18b20_write_byte(tl);

	// 写 config 字节
	ds18b20_write_byte(cfg);
}

// 读暂存器
// 读 9 字节暂存器数据，并进行 CRC 校验
// 校验通过，代表读取成功，返回 1，否则返回 0
unsigned char ds18b20_read_pad(DS18B20_SCRATCHPAD_PAD * pad)
{
	unsigned char i;

	// 写 Read Scratchpad 命令
	ds18b20_write_byte(DS18B20_CMD_READ_PAD);

	// 读 9 个字节的暂存器数据
	for(i = 0; i < 9; i ++)
	{
		// 按字节读
		pad -> pad_bytes[i] = ds18b20_read_byte();
	}

	// 对数据进行 CRC 校验，并返回校验结果
	return !crc8_check(pad -> pad_bytes, 9);
}

// 向 EEPROM 写入暂存数据
// 将暂存器的 TH、TL 寄存器以及配置寄存器内容复制到 EEPROM 中
// 复制过程可能持续 10ms (从寄生电源要求复制期间 10ms 高电平推测得来)
// 本方法在命令发出后立即返回，不等待命令执行完毕
void ds18b20_copy_pad()
{
	// 写 Copy Scratchpad 命令
	ds18b20_write_byte(DS18B20_CMD_COPY_PAD);
}

// 向 EEPROM 写入暂存数据
// 同上，但本方法将等待复制操作结束后返回
// 当复制完成时，返回 1，超时则返回 0
unsigned char ds18b20_copy_pad_sync()
{
	// 写 Copy Scratchpad 命令
	ds18b20_write_byte(DS18B20_CMD_COPY_PAD);

	// 等待操作完成，超时时间根据最大复制时间估计
	return ds18b20_wait_finish(11);
}

// 从 EEPROM 中重新加载数据
// 从 EEPROM 中重新读取 TH、TL 寄存器内容以及配置寄存器内容
// 本方法在命令发出后立即返回，不等待命令执行完毕
void ds18b20_recall_ee()
{
	// 写 Recall EE 命令
	ds18b20_write_byte(DS18B20_CMD_RECALL_EE);
}

// 从 EEPROM 中重新加载数据
// 同上，但本方法将等待操作结束后返回
// 当操作完成时，返回 1，超时则返回 0
unsigned char ds18b20_recall_ee_sync()
{
	// 写 Recall EE 命令
	ds18b20_write_byte(DS18B20_CMD_RECALL_EE);

	// 等待操作完成，超时时间同 ds18b20_copy_pad_sync 的超时（因为不知道操作要多久）
	return ds18b20_wait_finish(11);
}

// 读供电状态
// 若设备工作于寄生电源下，将返回 0，若工作于外部电源，将返回 1
unsigned char ds18b20_read_power()
{
	// 写 Read Power Supply 命令
	ds18b20_write_byte(DS18B20_CMD_READ_POWER);

	// 读 1 位数据并返回
	return ds18b20_read_bit();
}


/*----------  常用方法封装  ----------*/

// 初始化总线上所有设备
unsigned char ds18b20_init(DS18B20_TEMP_T th, DS18B20_TEMP_T tl, unsigned char bits)
{
	// 复位并检测存在
	if(!ds18b20_reset())
		return 0;

	// 跳过 ROM 搜索，向所有 ROM 设备发送指令
	ds18b20_skip_rom();

	// 写暂存器
	ds18b20_write_pad(th, tl, DS18B20_CONFIG_RESOLUTION(bits));

	return 1;
}

// 读取温度值，将结果通过指针写回变量
// 读取成功时返回 1，否则返回 0
// 【无复位、搜索操作】
unsigned char ds18b20_read_temp_bytes(DS18B20_TEMP_T * high, DS18B20_TEMP_T * low)
{
	DS18B20_SCRATCHPAD_PAD pad;

	// 读暂存器
	if(!ds18b20_read_pad(&pad))
		return 0;

	// 如果指针有效，则将结果写到指针所指的变量
	if(high)
	{
		* high = pad.pad_data.temp_high;
	}
	if(low)
	{
		* low = pad.pad_data.temp_low;
	}

	return 1;
}

// 读取用户字节，将结果通过指针写回变量
// 读取成功时返回 1，否则返回 0
// 在不使用温度报警功能时，TH 和 TL 寄存器可被用于存储两字节的用户数据
// 【无复位、搜索操作】
unsigned char ds18b20_read_user_bytes(unsigned char * byte1, unsigned char * byte2)
{
	DS18B20_SCRATCHPAD_PAD pad;

	// 读暂存器
	if(!ds18b20_read_pad(&pad))
		return 0;

	// 如果指针有效，则将结果写到指针所指的变量
	if(byte1)
	{
		* byte1 = pad.pad_data.th;
	}
	if(byte2)
	{
		* byte2 = pad.pad_data.tl;
	}

	return 1;
}

// 读取温度整数值
// 读取成功时返回温度的整数值，否则返回 DS18B20_INVALID_TEMP_INT
// 【无复位、搜索操作】
signed int ds18b20_read_int_temp()
{
	DS18B20_TEMP_T high, low;
	// 读温度字节值
	if(ds18b20_read_temp_bytes(&high, &low))
	{
		// 解析整数部分
		return DS18B20_TEMP_INT(high, low);
	}
	return DS18B20_INVALID_TEMP_INT;
}

// 读取温度浮点值
// 读取成功时返回温度的浮点数值，否则返回 DS18B20_INVALID_TEMP_FLOAT
// 【无复位、搜索操作】
float ds18b20_read_float_temp()
{
	DS18B20_TEMP_T high, low;
	// 读温度字节值
	if(ds18b20_read_temp_bytes(&high, &low))
	{
		// 解析整个数据
		return DS18B20_TEMP_FLOAT(high, low);
	}
	return DS18B20_INVALID_TEMP_FLOAT;
}
